#include "lib/decoders/caching/pvmvoicecaching.h"

#include "lib/pvmsystem.h"
#include "lib/memory/pvmmemory.h"

#include "pvm/pvmdebugapi.h"

PVMVoiceCaching::PVMVoiceCaching(IPVMVoice* sourceVoice, pvm_uint minBufferLengthMS, pvm_uint maxBufferLengthMS, pvm_uint renderStepInMS)
{
	PVMSpinLockInit(lock);

	srcVoice = sourceVoice;
	srcVoice->AddRef();

	this->bufferLengthMsMin = minBufferLengthMS;
	this->bufferLengthMsMax = maxBufferLengthMS;

	pvm_size bufferSize = getBufferLengthInBytes(maxBufferLengthMS);
	cacheBuffer = new PVMCircularBuffer(bufferSize);

	bufferLengthBytesMin = getBufferLengthInBytes(minBufferLengthMS);
	bufferLockSizeMax = getBufferLengthInBytes(renderStepInMS);
	bufferRenderSizeMax = bufferLockSizeMax;

	renderBuffer = (pvm_uint8*)PVMMemoryAlloc(bufferLockSizeMax);

	callsBeforFill = 0;
	idleRenderCalls = 0;
	samplesFilledFromLastIdle = 0;


	dbgMonIdBufferVal = -1;
	dbgMonIdBufferUnderflow = -1;
	dbgMonIdBufferFetchError = -1;
	dbgMonIdBufferDecodeSize = -1;
	dbgMonIdBufferFilledPartially = -1;
	dbgMonIdBufferFillSize = -1;
	
	TPVMDebugParamValue dbgMonMinVal;
	TPVMDebugParamValue dbgMonMaxVal;
	TPVMDebugParamValue dbgMonCurrVal;
	dbgMonMinVal.uParam = 0;
	dbgMonMaxVal.uParam = bufferSize;
	dbgMonCurrVal.uParam = 0;
	dbgMonIdBufferVal = PVMDebugMonitorParameterRegister(_PVM_STR("buff_state"), EPVM_TYPE_UINT, dbgMonMinVal, dbgMonMaxVal, dbgMonCurrVal);

	dbgMonIdBufferDecodeSize = PVMDebugMonitorParameterRegister(_PVM_STR("decode_size"), EPVM_TYPE_UINT, dbgMonMinVal, dbgMonMaxVal, dbgMonCurrVal);

	dbgMonMaxVal.uParam = 100;
	dbgMonCurrVal.uParam = 0;
	dbgMonIdBufferUnderflow = PVMDebugMonitorParameterRegister(_PVM_STR("underflow"), EPVM_TYPE_UINT, dbgMonMinVal, dbgMonMaxVal, dbgMonCurrVal);

	dbgMonMaxVal.uParam = 100;
	dbgMonCurrVal.uParam = 0;
	dbgMonIdBufferFetchError = PVMDebugMonitorParameterRegister(_PVM_STR("fetch_err"), EPVM_TYPE_UINT, dbgMonMinVal, dbgMonMaxVal, dbgMonCurrVal);
	
	dbgMonMaxVal.uParam = 100;
	dbgMonCurrVal.uParam = 0;
	dbgMonIdBufferFilledPartially = PVMDebugMonitorParameterRegister(_PVM_STR("partial_fill"), EPVM_TYPE_UINT, dbgMonMinVal, dbgMonMaxVal, dbgMonCurrVal);


	dbgMonMinVal.uParam = 0;
	dbgMonMaxVal.uParam = 10000;
	dbgMonCurrVal.uParam = 0;
	dbgMonIdBufferFillSize = PVMDebugMonitorParameterRegister(_PVM_STR("fill_size"), EPVM_TYPE_UINT, dbgMonMinVal, dbgMonMaxVal, dbgMonCurrVal);
	//*/
}

PVMVoiceCaching::~PVMVoiceCaching()
{
	PVMDebugMonitorParameterUnregister(dbgMonIdBufferVal);
	PVMDebugMonitorParameterUnregister(dbgMonIdBufferDecodeSize);
	PVMDebugMonitorParameterUnregister(dbgMonIdBufferUnderflow);
	PVMDebugMonitorParameterUnregister(dbgMonIdBufferFetchError);
	PVMDebugMonitorParameterUnregister(dbgMonIdBufferFilledPartially);
	PVMDebugMonitorParameterUnregister(dbgMonIdBufferFillSize);

	srcVoice->Release();
	srcVoice = NULL;

	delete cacheBuffer;
	cacheBuffer = NULL;

	PVMMemoryFree(renderBuffer);

	bufferLengthMsMin = 0;
	bufferLengthMsMax = 0;
}

pvm_uint PVMVoiceCaching::getBufferLengthInBytes(pvm_uint lengthInMS)
{
	pvm_uint length = (srcVoice->GetSamplesPerSecond() * srcVoice->GetChanelsNum() * (srcVoice->GetBitsPerSample() >> 3) * lengthInMS) / 1000;

	return length;
}

pvm_uint PVMVoiceCaching::getLengthInMS(pvm_uint lengthInSamples)
{
	lengthInSamples = lengthInSamples / srcVoice->GetChanelsNum();
	return lengthInSamples * 1000 / srcVoice->GetSamplesPerSecond();
}

void PVMVoiceCaching::dbgMonUpdateBufferState(pvm_uint currentSize)
{
	TPVMDebugParamValue dbgMonCurrVal;
	dbgMonCurrVal.uParam = currentSize;
	PVMDebugMonitorParameterUpdate(dbgMonIdBufferVal, dbgMonCurrVal);
}

PVMVoiceCaching* PVMVoiceCaching::Create(IPVMVoice* sourceVoice, pvm_uint minBufferLengthMS, pvm_uint maxBufferLengthMS, pvm_uint renderStepInMS)
{
	if(sourceVoice==NULL || minBufferLengthMS==0 || maxBufferLengthMS==0 || (minBufferLengthMS > maxBufferLengthMS))
		return NULL;

	PVMVoiceCaching* retVoice = new PVMVoiceCaching(sourceVoice, minBufferLengthMS, maxBufferLengthMS, renderStepInMS);

	return retVoice;
}

void PVMVoiceCaching::GetFormat(pvm_uint32 &samplesPerSecond, pvm_uint32 &bitsperSample, pvm_uint32 &chanels)
{
	srcVoice->GetFormat(samplesPerSecond, bitsperSample, chanels);
}
pvm_uint32 PVMVoiceCaching::GetSamplesPerSecond()
{
	return srcVoice->GetSamplesPerSecond();
}

pvm_uint32 PVMVoiceCaching::GetBitsPerSample()
{
	return srcVoice->GetBitsPerSample();
}

pvm_uint32 PVMVoiceCaching::GetChanelsNum()
{
	return srcVoice->GetChanelsNum();
}


pvm_uint32 PVMVoiceCaching::FillBuffer(void *buffer, pvm_uint32 numSamples)
{
	TPVMDebugParamValue dbgMonCurrVal;
	dbgMonCurrVal.uParam = numSamples;
	PVMDebugMonitorParameterUpdate(dbgMonIdBufferFillSize, dbgMonCurrVal);

	pvm_uint32 sampleSizeInBytes = srcVoice->GetBitsPerSample() >> 3;
	pvm_uint32 samplesToRender = numSamples;
	pvm_int8* bufCurrentPtr = (pvm_int8*)buffer;

	
	while(samplesToRender > 0)
	{
		if(cacheBuffer->getUsedSize() > 0)
		{
			PVMSpinLockAcquire(lock);
			pvm_int bytesFetched = cacheBuffer->fetchData(bufCurrentPtr, samplesToRender * sampleSizeInBytes);
			pvm_uint dbgUpdateSize = cacheBuffer->getUsedSize();
			PVMSpinLockRelease(lock);

			dbgMonUpdateBufferState(dbgUpdateSize);

			if(bytesFetched < 0)
			{
				dbgMonCurrVal.uParam = 100;
				PVMDebugMonitorParameterUpdate(dbgMonIdBufferFetchError, dbgMonCurrVal);
				return 0;
			}

			pvm_int samplesFetched = bytesFetched / sampleSizeInBytes;
			samplesToRender -= samplesFetched;
			samplesFilledFromLastIdle += samplesFetched;

			bufCurrentPtr += bytesFetched;
			continue;
		}
		else
		{
			TPVMDebugParamValue dbgMonCurrVal;
			dbgMonCurrVal.uParam = 100;
			PVMDebugMonitorParameterUpdate(dbgMonIdBufferUnderflow, dbgMonCurrVal);
			break;
		}
	}

	callsBeforFill++;
	pvm_uint32 samplesFilled = numSamples - samplesToRender;

	if(samplesToRender > 0)
	{
		TPVMDebugParamValue dbgMonCurrVal;
		dbgMonCurrVal.uParam = 100;
		PVMDebugMonitorParameterUpdate(dbgMonIdBufferFilledPartially, dbgMonCurrVal);
	}

	return samplesFilled;
}

void PVMVoiceCaching::SetPause(pvm_bool pause)
{
	srcVoice->SetPause(pause);
}

pvm_bool PVMVoiceCaching::GetPause()
{
	return srcVoice->GetPause();
}

pvm_bool PVMVoiceCaching::IsAlive()
{
	if(cacheBuffer->getUsedSize() > 0)
		return true;
	return srcVoice->IsAlive();
}

void PVMVoiceCaching::DoDestroy()
{
	delete this;
}

void PVMVoiceCaching::OnIdle()
{
	pvm_uint32 sampleSizeInBytes = srcVoice->GetBitsPerSample() >> 3;

	idleRenderCalls++;

	pvm_size sizeToRenderInBytes;
	pvm_size maxFreeSize;
	pvm_size usedSize;

	PVMSpinLockAcquire(lock);
	maxFreeSize = cacheBuffer->getFreeSize();
	usedSize = cacheBuffer->getUsedSize();
	PVMSpinLockRelease(lock);

	sizeToRenderInBytes = bufferRenderSizeMax;
	
	if(usedSize + sizeToRenderInBytes < bufferLengthBytesMin)
	{
		sizeToRenderInBytes = bufferLengthBytesMin - usedSize;
	}
	else if(sizeToRenderInBytes > maxFreeSize)
	{
		sizeToRenderInBytes = maxFreeSize;
	}

	if(sizeToRenderInBytes > 0)
	{
		TPVMDebugParamValue dbgMonCurrVal;
		dbgMonCurrVal.uParam = sizeToRenderInBytes;
		PVMDebugMonitorParameterUpdate(dbgMonIdBufferDecodeSize, dbgMonCurrVal);
	}


	while(sizeToRenderInBytes > 0)
	{
		samplesFilledFromLastIdle = 0;
		pvm_size lockSize = bufferLockSizeMax;
	
		if(lockSize > sizeToRenderInBytes)
			lockSize = sizeToRenderInBytes;

		pvm_uint32 samplesToRender = lockSize / sampleSizeInBytes;
		pvm_uint32 samplesFilled = srcVoice->FillBuffer(renderBuffer, samplesToRender);
		if(samplesFilled==0)
		{
			break;
		}


		pvm_uint renderedDataSize = samplesFilled * sampleSizeInBytes;
		pvm_uint bytesToCopyTotal = renderedDataSize;
		pvm_uint8* writePtr = renderBuffer;
		

		while(bytesToCopyTotal > 0)
		{
			pvm_uint bytesToWrite = bytesToCopyTotal;
			if(bytesToWrite > 1024)
				bytesToWrite = 1024;

			PVMSpinLockAcquire(lock);
			if(cacheBuffer->writeData(writePtr, bytesToWrite)!=(pvm_int)bytesToWrite)
			{
				//TODO:add some error handling
			}
			PVMSpinLockRelease(lock);

			writePtr += bytesToWrite;
			bytesToCopyTotal -= bytesToWrite;
		}


		pvm_uint dbgUpdateSize = cacheBuffer->getUsedSize();
		dbgMonUpdateBufferState(dbgUpdateSize);

		sizeToRenderInBytes -= renderedDataSize;
	}


	/*while(sizeToRenderInBytes > 0)
	{
		samplesFilledFromLastIdle = 0;
		pvm_size lockSize = 0;

		PVMSpinLockAcquire(lock);
		pvm_int8* bufPtr = cacheBuffer->lockBufferWrite(lockSize);

		if(!bufPtr)
		{
			PVMSpinLockRelease(lock);
			return;
		}

		if(lockSize > bufferLockSizeMax)
			lockSize = bufferLockSizeMax;

		pvm_uint32 samplesToRender = lockSize / sampleSizeInBytes;

		pvm_uint32 samplesFilled = srcVoice->FillBuffer(bufPtr, samplesToRender);
		if(samplesFilled==0)
		{
			cacheBuffer->unLockBufferWrite(0);
			PVMSpinLockRelease(lock);
			break;
		}

		lockSize = samplesFilled * sampleSizeInBytes;
		pvm_int unlockResult = cacheBuffer->unLockBufferWrite(lockSize);
		dbgMonUpdateBufferState();
		
		PVMSpinLockRelease(lock);
		if(!unlockResult)
			break;

		sizeToRenderInBytes -= lockSize;
	}*/

	callsBeforFill = 0;
}

void PVMVoiceCaching::SetLoopCount(pvm_int32 loopCount)
{
	//TODO:check case when loop decremented, but voice has gone to next loop
	srcVoice->SetLoopCount(loopCount);
}

pvm_int32 PVMVoiceCaching::GetLoopCount()
{
	return srcVoice->GetLoopCount();
}

pvm_bool PVMVoiceCaching::SetLoopInSamples(pvm_int64 loopFrom, pvm_int64 loopTo)
{
	//TODO: fix this code
	return srcVoice->SetLoopInSamples(loopFrom, loopTo);
}

pvm_bool PVMVoiceCaching::GetLoopInSamples(pvm_int64 &loopFrom, pvm_int64 &loopTo)
{
	return srcVoice->GetLoopInSamples(loopFrom, loopTo);
}

pvm_bool PVMVoiceCaching::GetLoopInMS(pvm_int32 &loopFrom, pvm_int32 &loopTo)
{
	return srcVoice->GetLoopInMS(loopFrom, loopTo);
}

pvm_bool PVMVoiceCaching::SetPositionMS(pvm_int32 position)
{
	return false;
}
pvm_int32 PVMVoiceCaching::GetPositionMS()
{
	return -1;
}

pvm_bool PVMVoiceCaching::GetOption(const pvm_char* optionName, void* optionVal, pvm_uint32* optionSize)
{
	return srcVoice->GetOption(optionName, optionVal, optionSize);
}

pvm_bool PVMVoiceCaching::SetOption(const pvm_char* optionName, void* optionVal, pvm_uint32 optionSize)
{
	return srcVoice->SetOption(optionName, optionVal, optionSize);
}
